iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0

Day29 要來做個倒數計時器

Custom Hook

function useTimer(initialTime: number): TimerHook {
  const [time, setTime] = useState<number>(initialTime);
  const [isRunning, setIsRunning] = useState<boolean>(false);
  const [endTime, setEndTime] = useState<Date | null>(null);
  const timerRef = useRef<number | null>(null);

  const startTimer = useCallback((seconds: number) => {
    // Clear any existing interval
    if (timerRef.current) {
      clearInterval(timerRef.current);
    }

    const now = Date.now();
    const then = now + seconds * 1000;
    setEndTime(new Date(then));
    setIsRunning(true);
    setTime(seconds);

    timerRef.current = window.setInterval(() => {
      setTime((prevTime) => {
        if (prevTime <= 1) {
          if (timerRef.current) clearInterval(timerRef.current);
          setIsRunning(false);
          return 0;
        }
        return prevTime - 1;
      });
    }, 1000);
  }, []);

  const stopTimer = useCallback(() => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
    }
    setIsRunning(false);
  }, []);

  return { time, isRunning, startTimer, stopTimer, endTime };
}

資料

type PresetButton = {
  time: number;
  label: string;
};

const presetButtons: PresetButton[] = [
  { time: 20, label: "20 Secs" },
  { time: 300, label: "Work 5" },
  { time: 900, label: "Quick 15" },
  { time: 1200, label: "Snack 20" },
  { time: 3600, label: "Lunch Break" },
];
  const { time, startTimer, endTime } = useTimer(0);

格式化時間

  const formatEndTime = (date: Date | null): string => {
    if (!date) {
      return "";
    }

    return `Be back at ${date.getHours()}:${date
      .getMinutes()
      .toString()
      .padStart(2, "0")}`;
  };

自訂時間

  const handleCustomTime = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const form = event.currentTarget;
    const minutesInput = form.elements.namedItem("minutes") as HTMLInputElement;
    const minutes = parseInt(minutesInput.value, 10);
    if (!isNaN(minutes)) {
      startTimer(minutes * 60);
    }
    form.reset();
  };

畫面結構

  return (
    <div className="bg-gradient-to-br from-blue-400 via-blue-500 to-blue-900 min-h-screen text-center">
      <div className="flex flex-col min-h-screen">
        <div className="flex">
          {presetButtons.map((preset) => (
            <button
              key={preset.time}
              type="button"
              onClick={() => startTimer(preset.time)}
              className="flex-1 bg-black bg-opacity-10 text-white text-2xl uppercase py-4 border-b-3 border-black border-opacity-20 border-r hover:bg-opacity-20 focus:outline-none"
            >
              {preset.label}
            </button>
          ))}
          <form onSubmit={handleCustomTime} className="flex-1 flex">
            <input
              type="text"
              name="minutes"
              placeholder="Enter Minutes"
              className="flex-1 bg-transparent border-0 text-2xl text-white placeholder-white placeholder-opacity-50 p-4 focus:outline-none"
            />
          </form>
        </div>

        <div className="flex-1 flex flex-col items-center justify-center">
          <h1 className="text-white text-[12rem] font-bold m-0 leading-none">
            {formatTime(time)}
          </h1>
          <p className="text-white text-4xl">{formatEndTime(endTime)}</p>
        </div>
      </div>
    </div>
  );

DEMO

https://codesandbox.io/p/devbox/9jlgk4


上一篇
[Day28]_Video-Speed-Controller
下一篇
[Day30]_Whack-A-Mole
系列文
React30——用 React 探索 JavaScript30 的魅力30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言